#ifndef __TPS_UTILS_H__
#define __TPS_UTILS_H__

#include "tps_tl.h"
#include "tps_non_copyable.h"

#include <stdexcept>
#include <memory>

#define TPS_REG_PROXY_CLASS(class_, id_) \
	template <> \
	struct ProxyRegItem<id_> \
	{ \
		typedef class_ Type; \
	};

#define TPS_REG_STUB_CLASS(class_, id_) \
	template <> \
	struct StubRegItem<id_> \
	{ \
		typedef class_ Type; \
	};

#define TPS_CREATE_MTD_INDEX(mtd_) \
	enum { mtd_##ID = sizeof(GetPSItemIndex(static_cast<TypeItemN *>(0)))}; \
	char (& GetPSItemIndex(Tps::Private::TypeItem<mtd_##ID> *))[mtd_##ID + 1];

namespace Tps
{

	namespace Private
	{

		template <typename T, typename NewThisType>
		struct MtdWrapTypeThisChanger;

		template
		<
			template <typename, typename, unsigned, bool> class WrapType,
			typename ThisType, typename MtdType, unsigned ParamsCount, bool IsConstMtd,
			typename NewThisType
		>
		struct MtdWrapTypeThisChanger<WrapType<ThisType, MtdType, ParamsCount, IsConstMtd>, NewThisType>
		{
			typedef WrapType<NewThisType, MtdType, ParamsCount, IsConstMtd> Type;
		};

		template <typename TThis, typename TBase>
		struct InheritFromProxyList
			: public InheritFromProxyList<TThis, typename TBase::Tail>
			, public MtdWrapTypeThisChanger<typename TBase::Head, TThis>::Type
		{
			virtual ~InheritFromProxyList()
			{
			};
		};

		template <typename TThis>
		struct InheritFromProxyList<TThis, NullType>
		{
			virtual ~InheritFromProxyList()
			{
			};
		};

		template <template <unsigned> class Item, unsigned BeginIndex, unsigned EndIndex>
		struct CreateMtdTypeList
		{
			typedef TypeList<typename Item<BeginIndex>::Type, typename CreateMtdTypeList<Item, BeginIndex + 1, EndIndex>::List> List;
		};

		template <template <unsigned> class Item, unsigned EndIndex>
		struct CreateMtdTypeList<Item, EndIndex, EndIndex>
		{
			typedef NullType List;
		};

		enum RefPtrVal
		{
			rpvAsRef = 0,
			rpvAsPtr = 1,
			rpvAsValue = 2
		};

		template <typename T>
		struct ParamSendBy
		{
			enum { SendType = rpvAsValue };
		};

		template <typename T>
		struct ParamSendBy<T &>
		{
			enum { SendType = rpvAsRef };
		};

		template <typename T>
		struct ParamSendBy<T *>
		{
			enum { SendType = rpvAsPtr };
		};

		template <typename StubType, typename RetType>
		struct StubCall
		{
			template <typename IFaceTYpe, typename UnpackerType, typename PackerType>
			static void Call(IFaceTYpe *iface, UnpackerType *unpacker, PackerType *packer)
			{
				packer->template PutRetValue<RetType>(StubType::Call(iface, unpacker));
			}
		};

		template <typename StubType>
		struct StubCall<StubType, void>
		{
			template <typename IFaceTYpe, typename UnpackerType, typename PackerType>
			static void Call(IFaceTYpe *iface, UnpackerType *unpacker, PackerType *packer)
			{
				StubType::Call(iface, unpacker);
			}
		};

		template <typename StubList, typename MtdIdType>
		struct CallStubMethodImpl
		{
			template <typename IFaceType, typename UnpackerType, typename PackerType>
			static void Call(IFaceType *iface, MtdIdType const &mtdId, UnpackerType *unpacker, PackerType *packer)
			{
				typedef typename StubList::Head StubType;
				typedef typename StubType::MethodType MethodType;

				if (StubType::StubMtdId == mtdId)
				{
					StubCall<StubType, typename MethodType::Ret>::Call(iface, unpacker, packer);
					return;
				}

				CallStubMethodImpl<typename StubList::Tail, MtdIdType>::Call(iface, mtdId, unpacker, packer);
			}
		};

		template <typename MtdIdType>
		struct CallStubMethodImpl<NullType, MtdIdType>
		{
			template <typename IFaceType, typename UnpackerType, typename PackerType>
			static void Call(IFaceType *iface, MtdIdType const &, UnpackerType *, PackerType *)
			{
				throw std::runtime_error("Unknown method");
			}
		};

		template <typename StubList, typename IFaceType, typename MtdIdType, typename UnpackerType, typename PackerType>
		void CallStubMethod(IFaceType *iface, MtdIdType const &mtdId, UnpackerType *unpacker, PackerType *packer)
		{
			CallStubMethodImpl<StubList, MtdIdType>::Call(iface, mtdId, unpacker, packer);
		}

		template <typename T, unsigned ParamsCount>
		struct CallStubImpl;

		template <typename T>
		struct CallStubImpl<T, 0>
		{
			template <typename IFaceType, typename IFaceMtdType, typename UnpackerType>
			static typename T::Ret Call(IFaceType *iface, IFaceMtdType mtd, UnpackerType *unpacker)
			{
				return (iface->*mtd)();
			}
		};

		template <typename T>
		struct CallStubImpl<T, 1>
		{
			template <typename IFaceType, typename IFaceMtdType, typename UnpackerType>
			static typename T::Ret Call(IFaceType *iface, IFaceMtdType mtd, UnpackerType *unpacker)
			{
				typename ConstCast<typename RemoveRef<typename T::Param1>::Type>::Type *Param1 = 0;

				unpacker->template GetParameter<typename T::Param1>(1, &Param1);

				return (iface->*mtd)(*Param1);
			}
		};

		template <typename T>
		struct CallStubImpl<T, 2>
		{
			template <typename IFaceType, typename IFaceMtdType, typename UnpackerType>
			static typename T::Ret Call(IFaceType *iface, IFaceMtdType mtd, UnpackerType *unpacker)
			{
				typename ConstCast<typename RemoveRef<typename T::Param1>::Type>::Type *Param1 = 0;
				typename ConstCast<typename RemoveRef<typename T::Param2>::Type>::Type *Param2 = 0;

				unpacker->template GetParameter<typename T::Param1>(1, &Param1);
				unpacker->template GetParameter<typename T::Param2>(2, &Param2);

				return (iface->*mtd)(*Param1, *Param2);
			}
		};

		template <typename T>
		struct CallStubImpl<T, 3>
		{
			template <typename IFaceType, typename IFaceMtdType, typename UnpackerType>
			static typename T::Ret Call(IFaceType *iface, IFaceMtdType mtd, UnpackerType *unpacker)
			{
				typename ConstCast<typename RemoveRef<typename T::Param1>::Type>::Type *Param1 = 0;
				typename ConstCast<typename RemoveRef<typename T::Param2>::Type>::Type *Param2 = 0;
				typename ConstCast<typename RemoveRef<typename T::Param3>::Type>::Type *Param3 = 0;

				unpacker->template GetParameter<typename T::Param1>(1, &Param1);
				unpacker->template GetParameter<typename T::Param2>(2, &Param2);
				unpacker->template GetParameter<typename T::Param3>(3, &Param3);

				return  (iface->*mtd)(*Param1, *Param2, *Param3);
			}
		};

		template <typename T>
		struct CallStubImpl<T, 4>
		{
			template <typename IFaceType, typename IFaceMtdType, typename UnpackerType>
			static typename T::Ret Call(IFaceType *iface, IFaceMtdType mtd, UnpackerType *unpacker)
			{
				typename ConstCast<typename RemoveRef<typename T::Param1>::Type>::Type *Param1 = 0;
				typename ConstCast<typename RemoveRef<typename T::Param2>::Type>::Type *Param2 = 0;
				typename ConstCast<typename RemoveRef<typename T::Param3>::Type>::Type *Param3 = 0;
				typename ConstCast<typename RemoveRef<typename T::Param4>::Type>::Type *Param4 = 0;
				unpacker->template GetParameter<typename T::Param1>(1, &Param1);
				unpacker->template GetParameter<typename T::Param2>(2, &Param2);
				unpacker->template GetParameter<typename T::Param3>(3, &Param3);
				unpacker->template GettParameter<typename T::Param4>(4, &Param4);

				return (iface->*mtd)(*Param1, *Param2, *Param3, *Param4);
			}
		};

		template <typename T>
		struct CallStubImpl<T, 5>
		{
			template <typename IFaceType, typename IFaceMtdType, typename UnpackerType>
			static typename T::Ret Call(IFaceType *iface, IFaceMtdType mtd, UnpackerType *unpacker)
			{
				typename ConstCast<typename RemoveRef<typename T::Param1>::Type>::Type *Param1 = 0;
				typename ConstCast<typename RemoveRef<typename T::Param2>::Type>::Type *Param2 = 0;
				typename ConstCast<typename RemoveRef<typename T::Param3>::Type>::Type *Param3 = 0;
				typename ConstCast<typename RemoveRef<typename T::Param4>::Type>::Type *Param4 = 0;
				typename ConstCast<typename RemoveRef<typename T::Param5>::Type>::Type *Param5 = 0;

				unpacker->template GetParameter<typename T::Param1>(1, &Param1);
				unpacker->template GetParameter<typename T::Param2>(2, &Param2);
				unpacker->template GetParameter<typename T::Param3>(3, &Param3);
				unpacker->template GetParameter<typename T::Param4>(4, &Param4);
				unpacker->template GetParameter<typename T::Param5>(5, &Param5);

				return (iface->*mtd)(*Param1, *Param2, *Param3, *Param4, *Param5);
			}
		};

		template <typename MtdType, typename IFaceType, typename IFaceMtdType, typename UnpackerType>
		typename MtdType::Ret CallStub(IFaceType *iface, IFaceMtdType mtd, UnpackerType *unpacker)
		{
			return CallStubImpl<MtdType, MtdType::ParamsCount>::Call(iface, mtd, unpacker);
		}
		
		template <typename T>
		void OutParamAssign(T* src, T &dest)
		{
		    dest = *src;
		}
		
		template <typename T>
		void OutParamAssign(T** src, T *dest)
		{
		    *dest = **src;
		}
		
        template
        <
            typename ParamType,
            bool needToGet =
                !IsConstType<typename TypeExtractor<ParamType>::Type>::IsConst &&
                static_cast<RefPtrVal>(ParamSendBy<ParamType>::SendType) != rpvAsValue
        >
        struct OutParamGetter
        {
            template <typename UnpackerType>
            static void Get(UnpackerType *unpacker, unsigned index, ParamType prm)
            {
                typename RemoveRef<ParamType>::Type *Prm = 0;
                unpacker->template GetParameter<ParamType>(index, &Prm);
                OutParamAssign(Prm, prm);
            }
        };
        
        template
        <
            typename ParamType
        >
        struct OutParamGetter<ParamType, false>
        {
            static void Get(...)
            {
            }
        };

        template <typename RetType>
        struct RetValueGetter
        {
            template <typename UnpackerType>
            static RetType Get(UnpackerType *unpacker)
            {
                typename ConstCast<typename RemoveRef<RetType>::Type>::Type *Ret = 0;
                unpacker->template GetRetValue<RetType>(&Ret);
                return *Ret;
            }
        };
        
        template <>
        struct RetValueGetter<void>
        {
            static void Get(...)
            {
            }
        };

        struct IDisposable
        {
            virtual ~IDisposable()
            {
            }
        };
        
        class IDisposableHolder
            : private NonCopyable
        {
        public:
            void Assign(IDisposable *object)
            {
                Object.reset(object);
            }
            template <typename T>
            T* Get() const
            {
                return static_cast<T *>(Object.get());
            }
            
        private:
            std::auto_ptr<IDisposable> Object;
        };

	}

}

#endif	// !__TPS_UTILS_H__
